package ms.sudoku.technique;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import ms.sudoku.PuzzleState;
import ms.sudoku.util.IntSet;

/**
 * Base type used for eliminating possible numbers from cells in a puzzle state.
 */
public abstract class EliminationTechnique {
	private static List<EliminationTechnique> allAvailableTechniques;

	public static List<EliminationTechnique> getAvailableTechniques() {
		if (allAvailableTechniques == null) {
			if (allAvailableTechniques == null) {
				allAvailableTechniques = new ArrayList<>();
				allAvailableTechniques.add(new BeginnerTechnique());
				allAvailableTechniques.add(new BlockAndColumnRowInteractionTechnique());
				allAvailableTechniques.add(new HiddenSingleTechnique());
				allAvailableTechniques.add(new NakedSingleTechnique());
				// _allAvailableTechniques.add(new HiddenSubsetTechnique());
				allAvailableTechniques.add(new NakedSingleTechnique());
				// _allAvailableTechniques.add(new NakedSubsetTechnique());
				allAvailableTechniques.add(new XwingTechnique());
				Collections.sort(allAvailableTechniques, new TechniqueDifficultyComparator());
			}
		}
		return allAvailableTechniques;
	}

	/**
	 * Gets the difficulty level of this technique.
	 */
	public abstract long getDifficultyLevel();

	/**
	 * Runs this elimination technique over the supplied puzzle state and
	 * previously computed possible numbers.
	 * 
	 * @param state
	 *            The puzzle state.
	 * @param exitEarlyWhenSoleFound
	 *            Whether the method can exit early when a cell with only one
	 *            possible number is found.
	 * @param possibleNumbers
	 *            The previously computed possible numbers.
	 * @param numberOfChanges
	 *            The number of changes made by this elimination technique.
	 * @param exitedEarly
	 *            Whether the method exited early due to a cell with only one
	 *            value being found.
	 * @return Whether more changes may be possible based on changes made during
	 *         this execution.
	 */
	public abstract boolean execute(PuzzleState state, boolean exitEarlyWhenSoleFound, IntSet[][] possibleNumbers,
			AtomicInteger numberOfChanges, AtomicBoolean exitedEarly);

	/**
	 * Gets an array of the possible number bit arrays for a given row in the
	 * puzzle state.
	 * 
	 * @param possibleNumbers
	 *            The possible numbers.
	 * @param row
	 *            The row.
	 * @param target
	 *            The array to store the output.
	 */
	public static void getRowPossibleNumbers(IntSet[][] possibleNumbers, int row, IntSet[] target) {
		for (int i = 0; i < target.length; i++) {
			target[i] = possibleNumbers[row][i];
		}
	}

	/**
	 * Gets an array of the possible number bit arrays for a given column in the
	 * puzzle state.
	 * 
	 * @param possibleNumbers
	 *            The possible numbers.
	 * @param column
	 *            The column.
	 * @param target
	 *            The array to store the output.
	 */
	public static void getColumnPossibleNumbers(IntSet[][] possibleNumbers, int column, IntSet[] target) {
		for (int i = 0; i < target.length; i++) {
			target[i] = possibleNumbers[i][column];
		}
	}

	/**
	 * Gets an array of the possible number bit arrays for a given box in the
	 * puzzle state.
	 * 
	 * @param state
	 *            The puzzle state.
	 * @param possibleNumbers
	 *            The possible numbers.
	 * @param boxIndex
	 *            The box.
	 * @param target
	 *            The array to store the output.
	 */
	public static void getBoxPossibleNumbers(PuzzleState state, IntSet[][] possibleNumbers, int boxIndex,
			IntSet[] target) {
		int count = 0;
		int boxSize = state.getBoxSize();
		int boxStartX = (boxIndex % boxSize) * boxSize;
		for (int row = boxStartX; row < boxStartX + boxSize; row++) {
			int boxStartY = (boxIndex / boxSize) * boxSize;
			for (int col = boxStartY; col < boxStartY + boxSize; col++) {
				target[count++] = possibleNumbers[row][col];
			}
		}
	}

	public String name() {
		return this.getClass().getSimpleName();
	}

	public String toString() {
		return this.name();
	}
}
